using System;
using System.Collections.Generic;
using System.Text;
using PSE.Hosting.Management.Services.ServicesManager;
using System.ServiceModel;
using System.Configuration;
using System.Threading;
using System.ServiceModel.Security;

namespace PSE.Hosting.Management.Common
{
    internal class WcfServices : IServices
    {
        private const string AccessDeniedExceptionMessage = "Access Denied. Verify if the current user is in WCF Services Managers or Administrators group.";
        private const string ServiceAddressFormatKey = "ServiceAddress";
        private static string _serviceAddressFormat;

        private const string EndpointNameConfigurationName = "NetTcpBinding_IServicesManager";

        internal WcfServices()
        {
            _serviceAddressFormat = ConfigurationManager.AppSettings[ServiceAddressFormatKey];
        }

        #region Package Methods

        /// <summary>
        /// Get a list of the packages created in a specific server.
        /// </summary>
        /// <param name="serverName">The name of the server.</param>
        /// <returns>A list of packages.</returns>
        public List<PackageData> GetPackageList(string serverName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            List<PackageData> packageList = null;

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    PackageList list = client.GetPackageList();
                    packageList = TranslatePackageData(list);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception ex)
                {
                    throw new GeneralException(ex.Message, ex);
                }
            }

            return packageList;
        }

        /// <summary>
        /// Adds a package to a specific server.
        /// </summary>
        /// <param name="serverName">The name of the server.</param>
        /// <param name="packageName">The name of the package to be created.</param>
        public void AddPackage(string serverName, string packageName, string domain, string userName, string password)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.AddPackage(packageName, domain, userName, password);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<PackageAlreadyExistsFault>)
                {
                    throw new PackageAlreadyExistsException();
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception ex)
                {
                    throw new GeneralException("Failed to add a package.", ex);
                }
            }
        }

        /// <summary>
        /// Removes a package from a specific server.
        /// </summary>
        /// <param name="serverName">The name of the server.</param>
        /// <param name="packageName">The name of the package to be removed.</param>
        public void UpdatePackage(string serverName, string packageName, string domain, string userName, string password)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.UpdatePackage(packageName, domain, userName, password);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to update the package.", ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception ex)
                {
                    throw new GeneralException("Failed to update the package.", ex);
                }
            }
        }

        /// <summary>
        /// Removes a package from a specific server.
        /// </summary>
        /// <param name="serverName">The name of the server.</param>
        /// <param name="packageName">The name of the package to be removed.</param>
        public void RemovePackage(string serverName, string packageName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.RemovePackage(packageName);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to remove the package.", ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception ex)
                {
                    throw new GeneralException("Failed to remove the package.", ex);
                }
            }
        }

        /// <summary>
        /// Starts all the assemblies from a specific package.
        /// </summary>
        /// <param name="serverName">The name of the server containing the package to be started.</param>
        /// <param name="packageName">The name of the package to be started.</param>
        public void StartPackage(string serverName, string packageName, bool startAssemblies)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StartPackage(packageName, startAssemblies);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<InvalidPackageFault> ex)
                {
                    throw new InvalidPackageException(ex.Message, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Starts all the assemblies from a specific package.
        /// </summary>
        /// <param name="serverName">The name of the server containing the package to be started.</param>
        /// <param name="packageName">The name of the package to be started.</param>
        public void StopPackage(string serverName, string packageName)
        {
            this.StopPackage(serverName, packageName, false);
        }

        /// <summary>
        /// Starts all the assemblies from a specific package.
        /// </summary>
        /// <param name="serverName">The name of the server containing the package to be started.</param>
        /// <param name="packageName">The name of the package to be started.</param>
        public void StopPackage(string serverName, string packageName, bool save)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StopPackage(packageName, save);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<InvalidPackageFault> ex)
                {
                    throw new InvalidPackageException(ex.Message, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Starts all the assemblies from a specific package asynchronously.
        /// </summary>
        /// <param name="serverName">The name of the server containing the package to be started.</param>
        /// <param name="packageName">The name of the package to be started.</param>
        public void StartPackageAsynchronous(string serverName, string packageName, bool startAssemblies)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StartPackageAsynchronous(packageName, startAssemblies);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (FaultException<InvalidPackageFault> ex)
                {
                    throw new InvalidPackageException(ex.Message, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        #endregion

        #region Assembly Methods

        /// <summary>
        /// Starts all WCF services from a specific assembly.
        /// </summary>
        /// <param name="serverName">The name of the server containing the assembly to start.</param>
        /// <param name="packageName">The name of the package of the assembly to start.</param>
        /// <param name="fileName">The name of the assembly file to start. This is the filename only, without the path.</param>
        public void StartAssembly(string serverName, string packageName, string fileName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StartAssembly(packageName, fileName);
                }
                catch (FaultException<InvalidAssemblyFault> ex)
                {
                    throw new InvalidAssemblyException(ex.Message, ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Stops all WCF services from a specific assembly.
        /// </summary>
        /// <param name="serverName">The name of the server containing the assembly to stop.</param>
        /// <param name="packageName">The name of the package containing the assembly to stop.</param>
        /// <param name="fileName">The name of the assembly file to stop. This is the filename only, without the path.</param>
        public void StopAssembly(string serverName, string packageName, string fileName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StopAssembly(packageName, fileName);
                }
                catch (FaultException<ServiceFault>)
                {
                    throw new GeneralException("Failed to stop the assembly.");
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Gets a list of all WCF assemblies of the specified packaged.
        /// </summary>
        /// <param name="serverName">The server name containing the assemblies to list.</param>
        /// <param name="packageName">The package name containing the assemblies to list.</param>
        /// <returns>A list of all WCF assemblies of the specified package.</returns>
        public List<AssemblyData> GetAssemblyList(string serverName, string packageName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            List<AssemblyData> assemblyList = null;

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    AssemblyList list = client.GetAssemblyList(packageName);
                    assemblyList = TranslateAssemblyData(list);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to get the assembly list.", ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }

            return assemblyList;
        }

        /// <summary>
        /// Adds an assembly to the Service Hoster.
        /// </summary>
        /// <param name="serverName">The name of the server where the Service Hoster is installed.</param>
        /// <param name="packageName">The name of the package where the assembly will be added.</param>
        /// <param name="assembly">The name of the assembly taht will be added.</param>
        /// <param name="codeBase">The path of the assembly that will be added.</param>
        /// <param name="domain">The domain name of the user to load the assembly. (Used only when the assembly must be loaded with elevated permissions).</param>
        /// <param name="userName">The user name to load the assembly. (Used only when the assembly must be loaded with elevated permissions).</param>
        /// <param name="password">The password of the user to load the assembly. (Used only when the assembly must be loaded with elevated permissions).</param>
        public void AddAssembly(string serverName, string packageName, string assembly, string codeBase/*, string domain, string userName, string password*/)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    // Add the assembly 
                    AddServiceRequest request = new AddServiceRequest();
                    request.PackageName = packageName;
                    request.Assembly = assembly;
                    request.CodeBase = codeBase;
                    //request.Domain = domain;
                    //request.UserName = userName;
                    //request.Password = password;
                    client.AddAssembly(request);
                }
                catch (FaultException<InvalidArgumentFault> ex)
                {
                    throw new ArgumentException(ex.Message, ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Removes an assembly from the Service Hoster.
        /// </summary>
        /// <param name="serverName">The name of the server where the assembly resides.</param>
        /// <param name="packageName">The name of the package containing the assembly to be removed.</param>
        /// <param name="fileName">The name of the assembly to be removed.</param>
        public void RemoveAssembly(string serverName, string packageName, string fileName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.RemoveAssembly(packageName, fileName);
                }
                catch (FaultException<InvalidArgumentFault> ex)
                {
                    throw new ArgumentException(ex.Message, ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        #endregion

        #region Service Methods

        /// <summary>
        /// Get a list of services from a specific assembly.
        /// </summary>
        /// <param name="serverName">The name of the server where the assembly resides.</param>
        /// <param name="packageName">The name of the package containing the assembly.</param>
        /// <param name="fileName">The name of the assembly to ge t the list of services.</param>
        /// <returns>A list of services from an assembly.</returns>
        public List<ServiceData> GetServiceList(string serverName, string packageName, string fileName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            List<ServiceData> serviceList = null;

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    ServiceList list = client.GetServicesList(packageName, fileName);
                    serviceList = TranslateServiceData(list);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to get the service list.", ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }

            return serviceList;
        }

        /// <summary>
        /// Starts a WCF service.
        /// </summary>
        /// <param name="serverName">The name of the server hosting the service.</param>
        /// <param name="packageName">The name of the package where the assembly containing the service is.</param>
        /// <param name="fileName">The name of the file containing the service.</param>
        /// <param name="serviceName">The name of the service to be started.</param>
        public void StartService(string serverName, string packageName, string fileName, string serviceName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    ServiceIdentifier service = new ServiceIdentifier();
                    service.PackageName = packageName;
                    service.Assembly = fileName;
                    service.ServiceName = serviceName;

                    client.Start(service);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to start service.", ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Stops a WCF service.
        /// </summary>
        /// <param name="serverName">The name of the server hosting the service.</param>
        /// <param name="packageName">The name of the package where the assembly containing the service is.</param>
        /// <param name="fileName">The name of the file containing the service.</param>
        /// <param name="serviceName">The name of the service to be stopped.</param>
        public void StopService(string serverName, string packageName, string fileName, string serviceName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    ServiceIdentifier service = new ServiceIdentifier();
                    service.PackageName = packageName;
                    service.Assembly = fileName;
                    service.ServiceName = serviceName;

                    client.Stop(service);
                }
                catch (FaultException<ServiceFault> ex)
                {
                    throw new GeneralException("Failed to stop service.", ex);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (CommunicationException ex)
                {
                    client.Abort();
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
                catch (Exception ex)
                {
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Starts the WCF Service Hoster Windows Service.
        /// </summary>
        /// <param name="serverName">The name of the server where the windows service is installed.</param>
        public void StartWindowsService(string serverName, bool asynchronous, bool startAll)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StartWindowsService(asynchronous, startAll);
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (Exception ex)
                {
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        /// <summary>
        /// Stops the WCF Service Hoster Windows Service.
        /// </summary>
        /// <param name="serverName">The name of the server where the windows service is installed.</param>
        public void StopWindowsService(string serverName)
        {
            string serviceAddress = this.GetServiceAddress(serverName);

            using (ServicesManagerClient client =
                new ServicesManagerClient(EndpointNameConfigurationName, serviceAddress))
            {
                try
                {
                    client.StopWindowsService();
                }
                catch (SecurityAccessDeniedException ex)
                {
                    throw new AccessDeniedException(AccessDeniedExceptionMessage, ex);
                }
                catch (Exception ex)
                {
                    throw new ServiceCommunicationException(serverName, ex.Message, ex);
                }
            }
        }

        #endregion

        private string GetServiceAddress(string serverName)
        {
            return string.Format(_serviceAddressFormat, serverName);
        }

        #region Translators

        private object Translate(Type targetType, object source)
        {
            return null;
        }

        private List<ServiceData> TranslateServiceData(ServiceList serviceList)
        {
            Service[] services = serviceList.Services;
            List<ServiceData> list = new List<ServiceData>();

            for (int i = 0; i < services.Length; i++)
            {
                ServiceData data = new ServiceData();
                data.Assembly = services[i].Assembly;
                data.CodeBase = services[i].CodeBase;
                data.IsAlive = services[i].IsAlive;
                data.IsValid = services[i].IsValid;
                data.MexAddress = services[i].MexAddress;
                data.Name = services[i].Name;
                data.UserName = services[i].UserName;

                list.Add(data);
            }

            return list;
        }

        private List<PackageData> TranslatePackageData(PackageList packageList)
        {
            Package[] packages = packageList.Packages;
            List<PackageData> list = new List<PackageData>();

            for (int i = 0; i < packages.Length; i++)
            {
                PackageData data = new PackageData();
                data.Id = packages[i].Id;
                data.Start = packages[i].Start;
                data.Name = packages[i].Name;
                data.Domain = packages[i].Domain;
                data.UserName = packages[i].UserName;

                list.Add(data);
            }

            return list;
        }

        private List<AssemblyData> TranslateAssemblyData(AssemblyList assemblyList)
        {
            Assembly[] assemblies = assemblyList.Assemblies;
            List<AssemblyData> list = new List<AssemblyData>();

            for (int i = 0; i < assemblies.Length; i++)
            {
                AssemblyData data = new AssemblyData();
                data.FileName = assemblies[i].FileName;
                data.Name = assemblies[i].Name;
                data.Path = assemblies[i].Path;
                //data.UserName = assemblies[i].UserName;

                list.Add(data);
            }

            return list;
        }

        #endregion

    }
}
